/**
 * 
 */
package ro.dta.idbi.model.dynamicpattern;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ro.dta.idbi.model.AbstractPattern;
import ro.dta.idbi.model.AbstractPrimitivePattern;
import ro.dta.idbi.model.Utils;

/**
 * Primitive BAV transformations parser
 * 
 * @author Tudor Dobrila
 * 
 */
public class PrimitiveParser {

	/**
	 * Regular expression patterns
	 */
	private static Hashtable<String, Pattern> patterns;

	/**
	 * Dependency extractors, used to extract dependencies between nodes in the
	 * dependency graph
	 */
	private static Hashtable<String, IDependencyExtractor> extractors;

	/**
	 * Regex for the BAV transformation
	 */
	private static Pattern pattern;

	static {
		pattern = Pattern.compile("^([a-zA-Z]+)[ ]*\\(([^:]+):[^\\)]+\\)$");

		patterns = new Hashtable<String, Pattern>();
		patterns.putAll(Utils.constructPatterns("add"));
		patterns.putAll(Utils.constructPatterns("delete"));
		patterns.putAll(Utils.constructPatterns("extend"));
		patterns.putAll(Utils.constructPatterns("contract"));
		patterns.putAll(Utils.constructRenamePatterns());

		// Initialise extractors factory
		extractors = new Hashtable<String, IDependencyExtractor>();
		extractors.put("atable", new AddTableExtractor());
		extractors.put("acolumn", new AddColumnExtractor());
		extractors.put("aprimarykey", new AddPkExtractor());
		extractors.put("aforeignkey", new AddFkExtractor());
		extractors.put("rtable", new RenameTableExtractor());
		extractors.put("rcolumn", new RenameColumnExtractor());
		extractors.put("rprimarykey", new RenamePkExtractor());
		extractors.put("rforeignkey", new RenameFkExtractor());
	}

	/**
	 * PrimitiveParser class constructor
	 */
	public PrimitiveParser() {
	}

	/**
	 * Parse a primitive BAV transformation, extracting the name of the object
	 * being modified, the operation performed (e.g. add, rename, etc.) and the
	 * set of dependent schema objects.
	 * 
	 * @return An atomic node representing this primitive BAV transformation
	 */
	public AtomicNode parse(String trans) {
		Matcher matcher = pattern.matcher(trans);

		if (matcher.matches()) {
			String command = matcher.group(1);
			String prefix;
			// Choose the appropriate parser, depending on the operation being
			// performed
			if ("rename".equals(command)) {
				prefix = "r";
			} else {
				prefix = "a";
			}
			String name = matcher.group(2);

			if (patterns.containsKey(command + name)) {
				Pattern p = patterns.get(command + name);
				Matcher matcher2 = p.matcher(trans);
				matcher2.find();
				// Run parser to extract atomic node from transformation
				return extractors.get(prefix + name).extractDepNode(command, matcher2);
			}
		}

		return null;
	}

	/**
	 * Interface for primitive parsers, used to parse a primitive BAV
	 * transformation and extract an atomic node from it
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static interface IDependencyExtractor {
		AtomicNode extractDepNode(String command, Matcher matcher);
	}

	/**
	 * Parser for the <i>add, delete, extract, contract</i> table primitive BAV
	 * transformations
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class AddTableExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_table");
			node.setName(matcher.group(1));
			node.getDependentObjects().addAll(getDependentsInQuery(matcher.group(2)));
			return node;
		}

	}

	/**
	 * Parser for the <i>rename</i> table primitive BAV transformation
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class RenameTableExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_table");
			node.setName(matcher.group(2));
			return node;
		}

	}

	/**
	 * Parser for the <i>add, delete, extract, contract</i> column primitive BAV
	 * transformations
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class AddColumnExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_column");
			node.setName("<<" + matcher.group(1) + "," + matcher.group(2) + ">>");
			node.getDependentObjects().addAll(getDependentsInQuery(matcher.group(5)));
			node.getDependentObjects().add("<<" + matcher.group(1) + ">>");
			return node;
		}
	}

	/**
	 * Parser for the <i>rename</i> column primitive BAV transformations
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class RenameColumnExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_column");
			node.setName(matcher.group(2));
			Entry<String, String> origCol = AbstractPattern.parseColName(matcher.group(1));
			node.getDependentObjects().add("<<" + origCol.getKey() + ">>");
			return node;
		}

	}

	/**
	 * Parser for the <i>add, delete, extract, contract</i> primary key
	 * primitive BAV transformations
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class AddPkExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_pk");
			node.setName(matcher.group(1));
			node.getDependentObjects().add("<<" + matcher.group(3) + ">>");
			List<String> cols = AbstractPrimitivePattern.getColumns(matcher.group(4));
			node.getDependentObjects().addAll(cols);
			return node;
		}
	}

	/**
	 * Parser for the <i>rename</i> primary key primitive BAV transformations
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class RenamePkExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_pk");
			node.setName(matcher.group(9));
			node.getDependentObjects().add("<<" + matcher.group(3) + ">>");
			List<String> cols = AbstractPrimitivePattern.getColumns(matcher.group(4));
			node.getDependentObjects().addAll(cols);
			return node;
		}
	}

	/**
	 * Parser for the <i>add, delete, extract, contract</i> foreign key
	 * primitive BAV transformations
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class AddFkExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_fk");
			node.setName(matcher.group(1));
			node.getDependentObjects().add("<<" + matcher.group(3) + ">>");
			List<String> cols = AbstractPrimitivePattern.getColumns(matcher.group(4));
			node.getDependentObjects().addAll(cols);
			node.getDependentObjects().add("<<" + matcher.group(8) + ">>");
			cols = AbstractPrimitivePattern.getColumns(matcher.group(9));
			node.getDependentObjects().addAll(cols);
			return node;
		}
	}

	/**
	 * Parser for the <i>rename</i> foreign key primitive BAV transformations
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class RenameFkExtractor implements IDependencyExtractor {

		@Override
		public AtomicNode extractDepNode(String command, Matcher matcher) {
			AtomicNode node = new AtomicNode();
			node.setType(command + "_fk");
			node.setName(matcher.group(14));
			node.getDependentObjects().add("<<" + matcher.group(3) + ">>");
			List<String> cols = AbstractPrimitivePattern.getColumns(matcher.group(4));
			node.getDependentObjects().addAll(cols);
			node.getDependentObjects().add("<<" + matcher.group(8) + ">>");
			cols = AbstractPrimitivePattern.getColumns(matcher.group(9));
			node.getDependentObjects().addAll(cols);
			return node;
		}
	}

	/**
	 * Given an IQL query, extract the set of dependent schema objects
	 * 
	 * @param iqlQuery
	 *            IQL query
	 * @return Set of dependent schema objects
	 */
	private static Set<String> getDependentsInQuery(String iqlQuery) {
		Set<String> result = new HashSet<String>();

		Pattern p = Pattern.compile(AbstractPattern.COL_REGEX);
		Matcher m = p.matcher(iqlQuery);
		while (m.find()) {
			for (int i = 0; i < m.groupCount() + 1; i++) {
				result.add(m.group(i));
			}
		}

		p = Pattern.compile(AbstractPattern.TABLE_REGEX);
		m = p.matcher(iqlQuery);
		while (m.find()) {
			for (int i = 0; i < m.groupCount() + 1; i++) {
				result.add(m.group(i));
			}
		}

		return result;
	}
}
